package com.tuniu.agents.common.controller;

import com.tuniu.agents.advisor.ChatEnvironmentMemory;
import com.tuniu.agents.agent.Agent;
import com.tuniu.agents.agent.AgentManager;
import com.tuniu.agents.common.AgentsConstants;
import com.tuniu.agents.common.IdGenerator;
import com.tuniu.agents.common.agent.CommandAgent;
import com.tuniu.agents.common.exception.ConversationBusinessException;
import com.tuniu.agents.common.i18n.I18nConstants;
import com.tuniu.agents.common.message.BaseAgentNewResp;
import com.tuniu.agents.common.message.ConversationManager;
import com.tuniu.agents.common.message.FluxSessionManager;
import com.tuniu.agents.common.message.MessageSenderManager;
import com.tuniu.agents.common.service.AntispamService;
import com.tuniu.agents.common.service.FileProcessService;
import com.tuniu.agents.common.service.FileProcessService.FileProcessResult;
import com.tuniu.agents.common.service.TraceService;
import com.tuniu.agents.common.util.NumberingUtil;
import com.tuniu.agents.evaluator.IEvaluator;
import com.tuniu.agents.evaluators.common.EvaluatorRegistry;
import com.tuniu.agents.impl.trip.chain.NetworkMode;
import com.tuniu.agents.impl.trip.chain.ProcessMode;
import com.tuniu.agents.impl.trip.chain.TripChain;
import com.tuniu.agents.message.MessageSender;
import com.tuniu.agents.util.I18nUtil;

import groovyjarjarpicocli.CommandLine.ExecutionException;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Locale;
import java.util.List;
import java.util.Map;

@RestController
@Slf4j
public class AssistantController {

    private final CommandAgent agent;

    private final ConversationManager conversationManager;

    @Autowired
    private IdGenerator idGenerator;

    @Autowired
    private AgentManager agentManager;

    /**
     * Antispam service
     */
    @Autowired
    private AntispamService antispamService;

    @Autowired
    private ChatEnvironmentMemory chatEnvironmentMemory;

    @Autowired
    private EvaluatorRegistry evaluatorRegistry;

    @Autowired
    public void setMessageSenderManager(MessageSender messageSender) {
        this.messageSenderManager = (MessageSenderManager) messageSender;
    }

    private MessageSenderManager messageSenderManager;

    @Autowired
    private TraceService traceService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private TripChain tripChain;

    @Autowired(required = false)
    private FileProcessService fileProcessService;  

    private static final String STOP_CONVERSATION_CHANNEL = "channel:stop_conversation";

    private final FluxSessionManager<String> sessionManager = new FluxSessionManager<>();

    public AssistantController(CommandAgent commandAgent,
                               @Qualifier("conversationHistoryManager") ConversationManager conversationManager) {
        this.agent = commandAgent;
        this.conversationManager = conversationManager;
    }

    /**
     * Main entry point
     * @param conversation_id Conversation ID
     * @param message Message
     * @return Output object
     */
    @RequestMapping(path = "/api/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(String conversation_id,
                           @RequestParam(value = "message", required = false) String message,
                           @RequestParam(value = "toolName", required = false) String toolName,
                           @RequestParam(value = "agentId", required = false) String agentId,
                           @RequestParam(value = "file", required = false) MultipartFile file) {
        return processChat(conversation_id, message, toolName, agentId, file, null);
    }

    @RequestMapping(path = "/api/chat", 
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatJson(String conversation_id, @RequestBody ChatRequest request) {
        return processChat(
            conversation_id,
            request.message(),
            request.toolName(),
            request.agentId(),
            null,
                null
        );
    }

    private Flux<String> processChat(String conversation_id, String message, String toolName, String agentId, MultipartFile file, String chatRequestId) {
        // Record trace information
        traceService.trace(conversation_id);

        // Generate request ID and response ID
        if(StringUtils.isEmpty(chatRequestId)) {
            chatRequestId = idGenerator.generateId();
        }
        final String requestId = chatRequestId;
        String responseId = idGenerator.generateId();
        NumberingUtil.reset(conversation_id);
        this.chatEnvironmentMemory.add(conversation_id,Map.of(AgentsConstants.AgentsConversationEnvironmentKey.CURRENT_REQUEST_ID,requestId));
        String antispamResult = antispamService.antispamCheck(conversation_id, message, requestId);
        if(StringUtils.isNotEmpty(antispamResult)){
            conversationManager.saveConversationRequest(conversation_id, requestId, responseId, message, 1);
            return Flux.just(BaseAgentNewResp.sendAnswerMsg(null, antispamResult));
        }

        // todo
        String languageTag = (String) chatEnvironmentMemory.get(conversation_id, AgentsConstants.AgentsConversationEnvironmentKey.ACCEPT_LANGUAGE);
        if(StringUtils.isEmpty(languageTag)){
            Locale locale = I18nUtil.getLocale();
            if (StringUtils.isNotEmpty(message)) {
                if (isChinese(message)) {
                    locale = Locale.CHINESE;
                }else {
                    locale = Locale.ENGLISH;
                }
            }
            I18nUtil.setLocaleForConversation(conversation_id, locale);
        }
        // Stream manager manages multi-segment returns, conversation records are stored
        conversationManager.saveConversationRequest(conversation_id, requestId, responseId, message, 0);
        String callResult = "";
        if (file != null && fileProcessService != null) {
            FileProcessResult fileProcessResult = new FileProcessResult();
            try {
                fileProcessResult = fileProcessService.processFile(file).get();
                if (fileProcessResult.getUploadUrl() != null) {
                    conversationManager.saveConversationRequest(conversation_id, requestId, responseId, fileProcessResult.getUploadUrl(), 0);
                }
            } catch (InterruptedException | ExecutionException | java.util.concurrent.ExecutionException e) {
                log.error("Error processing file", e);
            }
            // Call model to parse file content
            tripChain.setProcessMode(ProcessMode.BASIC,conversation_id);
            tripChain.setNetworkMode(NetworkMode.OFFLINE, conversation_id); // Initialize network mode
            callResult = tripChain.call(conversation_id, fileProcessResult.getProcessedContent());
        }
        String defaultMessage = callResult != null ? message + "\n" + callResult : message;

        // Get the calling agent
        Agent agent;
        if (StringUtils.isNotEmpty(agentId)) {
            // Fill in lastAgentId
            Map<String, Object> map = this.chatEnvironmentMemory.get(conversation_id);
            map.put(AgentsConstants.AgentsConversationEnvironmentKey.LAST_SELECTION_AGENT_ID, agentId);
            this.chatEnvironmentMemory.add(conversation_id, map);

            agent = agentManager.getAgent(agentId);
        } else {
            agent = this.agent;
        }

      

        // Store user's content in global variable
        this.chatEnvironmentMemory.add(conversation_id, Map.of(AgentsConstants.AgentsConversationEnvironmentKey.USER_LAST_MESSAGE, defaultMessage));

        return messageSenderManager.initConversation(() -> agent.stream(conversation_id, List.of(new UserMessage(defaultMessage)), null), conversation_id, responseId)
                .map(content -> conversationManager.saveConversationResponse(conversation_id, requestId, responseId, content))
                .concatWith(Mono.just(BaseAgentNewResp.sendFooterMsg(responseId, I18nUtil.getMessage(conversation_id, I18nConstants.FOOTER_CONTENT_MESSAGE_KEY)))
                        .map(footerContent -> conversationManager.saveConversationFooter(conversation_id, requestId, responseId, footerContent))
                        .concatWith(Mono.just(BaseAgentNewResp.sendStopMsg(responseId, I18nUtil.getMessage(conversation_id, I18nConstants.COMMON_ANTISPAM_ERROR_MESSAGE_KEY)))
                        .map(stopContent -> conversationManager.checkAntispam(conversation_id, requestId, responseId, stopContent))))
                .doOnComplete(() -> {
                    // Perform database storage operation at the end of the stream
                    conversationManager.streamToDatabase(conversation_id, requestId, responseId);
                    messageSenderManager.removeConversation(conversation_id);
                });
    }

    /**
     * Stop conversation
     * @param conversationId Conversation ID
     * @return
     */
    @RequestMapping(path = "/api/chat/stop/{conversationId}")
    public void stopChat(@PathVariable("conversationId") String conversationId) {
        // Attempt to clean up session resources locally
        // Use modified stopConversation method to cancel and clean up session
        boolean localCleanupSuccess = messageSenderManager.stopConversation(conversationId);
        if (localCleanupSuccess) {
            log.info("Conversation {} has been successfully stopped and local resources cleaned up", conversationId);
        } else {
            // Publish stop conversation event
            redisTemplate.convertAndSend(STOP_CONVERSATION_CHANNEL, conversationId);
            log.info("Local cleanup failed for conversation {}, notifying other nodes to clean resources", conversationId);
        }
    }

    public record ChatRequest (String message, String toolName, String agentId) {
    }

    @RequestMapping(path="/api/evaluator/{agentId}")
    public Flux<String> doEvaluator(@PathVariable("agentId") String agentId, String userId) {
        try {
            IEvaluator evaluator = evaluatorRegistry.getEvaluatorByName(agentId.toUpperCase());
            return evaluator.evaluateFlux(userId);
        } catch (Exception e) {
            log.error("doEvaluator error, agentId:{}, userId:{}, e:", agentId, userId, e);
            return Flux.empty();
        }
    }

    @RequestMapping(path="/api/getGlobalVariable/{type}")
    public String doEvaluator(@PathVariable("type") Integer type) {
        // Implementation details
        return null;
    }

    @RequestMapping(path = "/api/heartCheck", method = RequestMethod.GET)
    public String ping() {
        return "success";
    }

    

    /**
     * Handle custom conversation business exceptions
     */
    @ExceptionHandler(ConversationBusinessException.class)
    @ResponseStatus(HttpStatus.OK)
    public String handleBusinessException(ConversationBusinessException cex, HttpServletRequest request) {
        String path = request.getRequestURI();
        log.error("Business error occurred at path {}: ", path, cex);
        String userFriendlyMessage = org.apache.commons.lang3.StringUtils.isBlank(cex.getUserFriendlyMessage())
                ? I18nUtil.getMessage(I18nConstants.COMMON_GLOBAL_ERROR_MESSAGE_KEY)
                : cex.getMessage();
        String sendAnswerMsg = BaseAgentNewResp.sendAnswerMsg("", userFriendlyMessage);
        conversationManager.saveConversationResponse(cex.getConversationId(), sendAnswerMsg);
        return sendAnswerMsg;
    }

    /**
     * Handle general exceptions
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    public String handleException(Exception ex, HttpServletRequest request) {
        String path = request.getRequestURI();
        String userFriendlyMessage = I18nUtil.getMessage(I18nConstants.COMMON_GLOBAL_ERROR_MESSAGE_KEY);
        log.error("General error occurred at path {}: ", path, ex);
        if (path.contains("/api/evaluator/")) {
            return Strings.EMPTY;
        }
        return BaseAgentNewResp.sendAnswerMsg("", userFriendlyMessage);
    }

    /**
     * Check if a string contains Chinese characters
     */
    private boolean isChinese(String text) {
        for (char c : text.toCharArray()) {
            if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
                return true;
            }
        }
        return false;
    }
}